import cv2
import requests
import time
import base64
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
import numpy
from configparser import ConfigParser
import codecs
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

cfg = ConfigParser()
cfg.read_file(codecs.open('config.ini', "r", "utf-8-sig"))
english_to_name = dict()


def get_token():
    ak = cfg.get('baidu', 'ak')
    sk = cfg.get('baidu', 'sk')
    url = "https://aip.baidubce.com/oauth/2.0/token"
    params = dict()
    params['grant_type'] = "client_credentials"
    params['client_id'] = ak
    params['client_secret'] = sk
    response = requests.post(url, params)
    response.encoding = 'utf-8'
    if response.status_code == 200:
        try:
            logger.info("获取token成功")
            return response.json()['access_token']
        except:
            raise AssertionError("获取token失败，请检查ak和sk是否正确", response.text)
    else:
        raise AssertionError("获取token失败", response.json()[''])


def load():
    global english_to_name
    global capture, cascade
    capture = cv2.VideoCapture(0)
    cascade = cv2.CascadeClassifier("./haarcascade_frontalface_default.xml")  ## 读入分类器数据
    english_to_name = dict(cfg['relative'])
    logger.info("配置文件加载完成")


def get_picture():
    ret, frame = capture.read()
    cv2.destroyAllWindows()
    cv2.imshow('',frame)
    cv2.waitKey(10)

    if ret:
        return frame
    else:
        send("无法读取摄像头图像")
        raise AssertionError("无法读取摄像头图像")


def get_sm_token():
    params = {'username': cfg.get('sm', 'username'), 'password': cfg.get('sm', 'password')}
    response = requests.post('https://sm.ms/api/v2/token', params)
    if response.json()['success']:
        logger.info("获取图床token成功")
        return response.json()['data']['token']
    else:
        send("获取图床token失败" + str(response.json()['message']))
        raise AssertionError("获取图床token失败"+ str(response.json()['message']))


def upload(token, frame):
    img = Image.fromarray(frame)  # 将每一帧转为Image
    bytes_io = BytesIO()  # 创建一个BytesIO
    img.save(bytes_io, format='JPEG')  # 写入output_buffer
    headers = {'Authorization': token}
    params = dict()
    params['smfile'] = bytes_io.getvalue()
    response = requests.post('https://sm.ms/api/v2/upload', files=params, headers=headers)
    if response.json()['success']:
        url = response.json()['data']['url']
        return url
    else:
        send("图片上传失败" + str(response.json()['message']))
        raise AssertionError("图片上传失败" + str(response.json()['message']))


def have_face(image_base64,token):
    url = "https://aip.baidubce.com/rest/2.0/face/v3/detect?access_token=" + token
    headers = {
        'Content-Type': "application/json"
    }
    params = dict()
    params['image'] = image_base64
    params['image_type'] = "BASE64"
    params['group_id_list'] = "relative"
    response = requests.post(url, params, headers=headers)
    if response.status_code == 200:
        return response.json()['error_code'] == 0
    else:
        send("请求人脸检测API失败")
        raise AssertionError("请求人脸检测API失败")


def send(message):
    url = 'https://sc.ftqq.com/{}.send'.format(cfg.get('server', 'SCKEY'))
    params = dict()
    params['text'] = '客人来访通知'
    params['desp'] = message
    response = requests.post(url, params)
    response.encoding = 'utf-8'
    if response.status_code == 200:
       logger.info("发送消息成功")
    else:
        logger.critical("无法发送消息")


def get_api(token, image_base64):
    url = "https://aip.baidubce.com/rest/2.0/face/v3/multi-search?access_token=" + token
    headers = {
        'Content-Type': "application/json"
    }
    params = dict()
    params['image'] = image_base64
    params['image_type'] = "BASE64"
    params['group_id_list'] = "relative"
    response = requests.post(url, params, headers=headers)
    if response.status_code == 200:
        return response.json()
    else:
        send("请求API失败")
        raise AssertionError("请求API失败")


def get_base64(frame):
    img = Image.fromarray(frame)  # 将每一帧转为Image
    output_buffer = BytesIO()  # 创建一个BytesIO
    img.save(output_buffer, format='JPEG')  # 写入output_buffer
    byte_data = output_buffer.getvalue()  # 在内存中读取
    base64_data = base64.b64encode(byte_data)  # 转为BASE64
    return base64_data  # 转码成功 返回base64编码


def cv2ImgAddText(img, text, left, top, textColor=(255, 255, 0), textSize=20):
    if (isinstance(img, numpy.ndarray)):
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    draw = ImageDraw.Draw(img)
    fontStyle = ImageFont.truetype(
        "font/simsun.ttc", textSize, encoding="utf-8")
    draw.text((left, top), text, textColor, font=fontStyle)
    return cv2.cvtColor(numpy.asarray(img), cv2.COLOR_RGB2BGR)


def handle_response(sm_token, frame, response):
    token = ''
    err_code = response['error_code']
    success = False
    if err_code == 0:
        result = response['result']
        message = "{}共识别到{}个人脸.".format(time.strftime("%H:%M"), result['face_num'])
        face_list = result['face_list']
        i = 1
        for face in face_list:
            name = face['user_list'][0]['user_id']
            if name in english_to_name:
                name = english_to_name[name]
            xs = int(face['location']['left'])
            ys = int(face['location']['top'])
            xe = int(xs + face['location']['width'])
            ye = int(ys + face['location']['height'])
            score = face['user_list'][0]['score']
            content = "第{}个人脸距离左边界{}，上边界{},识别结果为：{},概率：{:.2f}.".format(i, xs, ys, name, score)
            message += content
            cv2.rectangle(frame, (xs, ys), (xe, ye), (255, 0, 0), 2)
            frame = cv2ImgAddText(frame, name, xs, ys)
        url = upload(sm_token, frame)
        message += "![]({})".format(url)
        send(message)
        success = True
    elif err_code == 110:
        token = get_token()
    else:
        logger.warning("人脸识别失败，错误码{},错误信息{}".format(response['error_code'], response['error_msg']))
    return token,success


if __name__ == "__main__":
    load()
    token = get_token()
    sm_token = get_sm_token()
    global capture
    while True:
        frame = get_picture()
        image_base64 = get_base64(frame)
        if have_face(image_base64,token):
            logger.info("已检测到人脸")
            response = get_api(token, image_base64)
            refresh_token,success = handle_response(sm_token, frame, response)
            if refresh_token != '':
                token = refresh_token
                continue
            if not success:
                continue
            time.sleep(5)
        else:
            time.sleep(1)
